﻿using System;
using NUnit.Framework;
using NUnit.Framework.SyntaxHelpers;
using SimpleExpressionEvaluator.Compilation;
using SimpleExpressionEvaluator.Evaluation;
using SimpleExpressionEvaluator.Parser;
using SimpleExpressionEvaluator.Parsers.Irony;

namespace SimpleExpressionEvaluator.Tests.Parser
{
    [TestFixture]
    public class NativeExpressionCompilerTestFixture
    {
        [Test]
        public void Can_Compile_Simple_Number()
        {
            const string expression = "1.1";
            var compiler = new ExpressionCompiler();
            IExpression compiled = compiler.CompileExpression<double>(expression);
            Assert.That(compiled.Evaluate(null), Is.EqualTo(1.1));
        }

        [Test]
        public void Can_Compile_Simple_String()
        {
            const string expression = "\"1.1\"";
            var compiler = new ExpressionCompiler();
            IExpression compiled = compiler.CompileExpression<string>(expression);
            Assert.That(compiled.Evaluate(null), Is.EqualTo("1.1"));
        }

        [Test]
        public void Can_Compile_Simple_Bool()
        {
            const string expression = "tRue";
            var compiler = new ExpressionCompiler();
            IExpression compiled = compiler.CompileExpression<bool>(expression);
            Assert.That(compiled.Evaluate(null), Is.EqualTo(true));
        }

        [Test]
        public void Can_Compile_Simple_Variable()
        {
            const string expression = "varName";
            var compiler = new ExpressionCompiler();
            IExpression compiled = compiler.CompileExpression<string>(expression);
            Assert.That(compiled.Evaluate(null), Is.EqualTo("varName"));
        }

        [Test]
        public void Can_Compile_Uknown_Variable()
        {
            const string expression = "varName";
            var compiler = new ExpressionCompiler();
            IExpression compiled = compiler.CompileExpression<string>(expression);

            var context = new ExpressionContext(null);
            context.ResolveUnknownVariable += (sender, args) =>
                                                  {
                                                      Assert.That(args.VariableName, Is.EqualTo("varName"));
                                                      args.VariableValue = 123.456;
                                                  };
            Assert.That(compiled.Evaluate(context), Is.EqualTo("123.456"));

        }

        [Test]
        public void Will_Convert_Result_When_Appropriate()
        {
            const string expression = "\"true\"";
            var compiler = new ExpressionCompiler();
            IExpression expr = compiler.CompileExpression<string>(expression);
            Assert.That(expr.Evaluate(null), Is.EqualTo("true"));

            expr = compiler.CompileExpression<bool>(expression);
            Assert.That(expr.Evaluate(null), Is.EqualTo(true));
        }

        [Test]
        public void Can_Compile_Unary_Expression()
        {
            const string expression = "!true";
            var compiler = new ExpressionCompiler();
            IExpression expr = compiler.CompileExpression<bool>(expression);
            Assert.That(expr.Evaluate(null), Is.False);
        }

        [Test]
        public void Can_Compile_Binary_Expression()
        {
            const string expression = "1+2";
            var compiler = new ExpressionCompiler();
            IExpression expr = compiler.CompileExpression<int>(expression);
            Assert.That(expr.Evaluate(null), Is.EqualTo(3));
        }

        [Test]
        public void Can_Compile_Qualified_Name()
        {
            const string expression = "hello.goodbye.darlin.boy";
            var compiler = new ExpressionCompiler();
            IExpression expr = compiler.CompileExpression<string>(expression);
            var context = new ExpressionContext(null);
            context.ResolveUnknownQualifiedName += (sender, args) =>
                                                       {
                                                           Assert.That(args.QualifiedName.Length, Is.EqualTo(4));
                                                           Assert.That(args.QualifiedName[0], Is.EqualTo("hello"));
                                                           Assert.That(args.QualifiedName[1], Is.EqualTo("goodbye"));
                                                           Assert.That(args.QualifiedName[2], Is.EqualTo("darlin"));
                                                           Assert.That(args.QualifiedName[3], Is.EqualTo("boy"));
                                                           args.Value = "GitErDone";
                                                       };
            Assert.That(expr.Evaluate(context), Is.EqualTo("GitErDone"));
        }
      

        [Test]
        public void Can_Compile_Paren_Groups()
        {
            string expression = "2 + 5 * 3";
            var compiler = new ExpressionCompiler();
            IExpression expr = compiler.CompileExpression<int>(expression);
            Assert.That(expr.Evaluate(null), Is.EqualTo(17));
            expression = "( 2 + 5 ) * 3";
            expr = compiler.CompileExpression<int>(expression);
            Assert.That(expr.Evaluate(null), Is.EqualTo(21));

        }

        [Test]
        public void Can_Compile_Unknown_Function()
        {
            const string expression = "2 + func(3+5)";
            var compiler = new ExpressionCompiler();
            IExpression expr = compiler.CompileExpression<int>(expression);
            var context = new ExpressionContext();
            context.ResolveMissingFunction += (sender, args) =>
                                                  {
                                                      args.Function = functionArgs => functionArgs[0];
                                                  };
            Assert.That(expr.Evaluate(context), Is.EqualTo(10));

        }

        [Test]
        public void Can_Compile_Function()
        {
            const string expression = "Today()";
            var compiler = new ExpressionCompiler();
            IExpression expr = compiler.CompileExpression<DateTime>(expression);
            Assert.That(expr.Evaluate(null), Is.EqualTo(DateTime.Today));
        }

        [Test]
        public void Can_Compile_Expr_With_Generic_Return()
        {
            const string expression = "Today()";
            var compiler = new ExpressionCompiler();
            IExpression expr = compiler.CompileExpression<object>(expression);
            Assert.That(expr.Evaluate(null), Is.EqualTo(DateTime.Today));
        }

        [Test]
        public void Can_Compile_Aggregate_Function()
        {
            const string expression = "Sum()";
            var compiler = new ExpressionCompiler();
            IExpression expr = compiler.CompileExpression<double>(expression);
            Assert.That(expr.Evaluate(new ExpressionContext(new[] { 1, 2, 3 })), Is.EqualTo(6));
        }

        [Test]
        public void Can_Parse_Basic_Conditional()
        {
            const string expression = "if (true) then 1";
            var compiler = new ExpressionCompiler();
            IExpression expr = compiler.CompileExpression<int>(expression);
            Assert.That(expr.Evaluate(null), Is.EqualTo(1));
        }

        [Test]
        public void Can_Parse_Basic_Conditional_False_Result()
        {
            const string expression = "if (false) then 1";
            var compiler = new ExpressionCompiler();
            IExpression expr = compiler.CompileExpression<int>(expression);
            Assert.That(expr.Evaluate(null), Is.EqualTo(0));
        }

        [Test]
        public void Can_Parse_If_Then_Else()
        {
            const string expression = "if (false) then 1 else 2";
            var compiler = new ExpressionCompiler();
            IExpression expr = compiler.CompileExpression<int>(expression);
            Assert.That(expr.Evaluate(null), Is.EqualTo(2));
        }

        [Test]
        public void Can_Parse_If_Then_Else_2()
        {
            const string expression = "if (true) then \"red\" else \"black\"";
            var compiler = new ExpressionCompiler();
            IExpression expr = compiler.CompileExpression<string>(expression);
            Assert.That(expr.Evaluate(null), Is.EqualTo("red"));
        }

        [Test]
        public void Can_Parse_If_Then_Else_In_Complex_Context()
        {
            const string expression = "2 * (if (true) then 9 otherwise 5 + 3)^2";
            var compiler = new ExpressionCompiler();
            IExpression expr = compiler.CompileExpression<int>(expression);
            Assert.That(expr.Evaluate(null),Is.EqualTo(162));
        }

        [Test]
        public void Can_Parse_Array_Expression()
        {
            const string expression = "current[1]";
            var data = new[] {"a", "x", "d"};
            var compiler = new ExpressionCompiler();
            IExpression expr = compiler.CompileExpression<string>(expression);
            Assert.That(expr.Evaluate(new ExpressionContext(data)), Is.EqualTo("x"));
        }
    }
}